/*
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */

package com.facebook.drawee.components;

import java.util.HashSet;
import java.util.Set;

import android.os.Handler;
import android.os.Looper;

/**
 * Component that defers {@code release} until after the main Looper has completed its current
 * message. Although we would like for defer {@code release} to happen immediately after the current
 * message is done, this is not guaranteed as there might be other messages after the current one,
 * but before the deferred one, pending in the Looper's queue.
 * <p/>
 * onDetach / onAttach events are used for releasing / acquiring resources. However, sometimes we
 * get an onDetach event followed by an onAttach event within the same loop. In order to avoid
 * overaggressive resource releasing / acquiring, we defer releasing. If onAttach happens within
 * the same loop, we will simply cancel corresponding deferred release, avoiding an unnecessary
 * resource release / acquire cycle. If onAttach doesn't happen before the deferred message gets
 * executed, the resources will be released.
 * <p/>
 * This class is not thread-safe and should only be used from the main thread (UI thread).
 */
public class DeferredReleaser {

    private static DeferredReleaser sInstance = null;

    public static synchronized DeferredReleaser getInstance() {
        if (sInstance == null) {
            sInstance = new DeferredReleaser();
        }
        return sInstance;
    }

    public interface Releasable {
        public void release();
    }

    private final Set<Releasable> mPendingReleasables;
    private final Handler mUiHandler;

    public DeferredReleaser() {
        mPendingReleasables = new HashSet<Releasable>();
        mUiHandler = new Handler(Looper.getMainLooper());
    }

    /*
     * Walks through the set of pending releasables, and calls release on them.
     * Resets the pending list to an empty list when done.
     */
    private final Runnable releaseRunnable = new Runnable() {
        @Override
        public void run() {
            for (Releasable releasable : mPendingReleasables) {
                releasable.release();
            }
            mPendingReleasables.clear();
        }
    };

    /**
     * Schedules deferred release.
     * <p/>
     * The object will be released after the current Looper's loop,
     * unless {@code cancelDeferredRelease} is called before then.
     *
     * @param releasable Object to release.
     */
    public void scheduleDeferredRelease(Releasable releasable) {
        if (!mPendingReleasables.add(releasable)) {
            return;
        }
        // Posting to the UI queue is an O(n) operation, so we only do it once.
        // The one runnable does all the releases.
        if (mPendingReleasables.size() == 1) {
            mUiHandler.post(releaseRunnable);
        }
    }

    /**
     * Cancels a pending release for this object.
     *
     * @param releasable Object to cancel release of.
     */
    public void cancelDeferredRelease(Releasable releasable) {
        mPendingReleasables.remove(releasable);
    }

}
